
import math

from math import floor
from Function import *

class P2:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __str__(self):
        return "(" + str(self.x) + "," + str(self.y) + ")"
    def __add__(self, p):
        return P2(self.x + p.x, self.y+ p.y)
    def __rmul__(self, p):
        return P2(self.x * p, self.y * p)
    def __mul__(self, p):
        return P2(self.x * p, self.y * p)
    def __div__(self, p):
        return P2(self.x / p, self.y / p)
    def __sub__(self, p):
        return P2(self.x - p.x, self.y - p.y)
    def dist(self, p):
        return abs(self -p)
    def __abs__(x):
        return math.sqrt(x.x * x.x + x.y *x.y)
    def toPoint(self, p):
        return (Point(p.x, p.y))
    def rotate (self, theta):
        s = math.sin(theta)
        c = math.cos(theta)
        return P2(self.x * (c) * (-s), self.y * (s * c)) 
    def scale2(self, p):
        return P2(self.x * p.x, self.y * p.y)
    def lerp(self, p, t):
        newx = (1 - t) * self.x + (t * p.x)
        newy = (1 - t) * self.y + (t * p.y)
        return P2(newx, newy)
    def mapx(self, f):
        return P2(f(self.x), self.y)
    def mapy(self, f):
        return P2(self.x, f(self.y))
    def mapxy(self, f):
        return P2(f(self.x), f(self.y)) 
    def mapr(self, f):
        r = math.sqrt(self.x * self.x + self.y * self.y)
        newr = f(r)
        return (self * (newr/r if r != 0 else 0))
    def angle(self):
        theta = math.atan2(self.y, self.x)
        return theta
    def dot(self, p):
        return self.x * p.x + self.y * p.y 

class P3:
    def __init__(self, x, y, z):
        self.x = x
        self.y = y
        self.z = z
    def __str__(self):
        return "(" + str(self.x) + "," + str(self.y) +"," + str(self.z) + ")"
    def __add__(self, p):
        return P3(self.x + p.x, self.y+ p.y, self.z + p.z)
    def __mul__(self, p):
        return P3(self.x * p, self.y * p, self.z * p)
    def __rmul__(self, p):
        return P3(self.x * p, self.y * p, self.z * p)
    def __div__(self, c):
        return self * (1.0/c)
    def __sub__(self, p):
        return P3(self.x - p.x, self.y - p.y, self.z - p.z)
    def __rsub__(self, p):
        return P3(self.x - p.x, self.y - p.y, self.z - p.z)
    def dist(self, p):
        return abs(self -p)
    def __abs__(x):
        return math.sqrt(x.x * x.x + x.y * x.y + x.z * x.z)
    def toPoint(self, p):
        return (Point(p.x, p.y))
    def rotate (self, theta):
        s = math.sin(theta)
        c = math.cos(theta)
        return P3(self.x * (c) * (-s), self.y * (s * c), self.z) 
    def scale2(self, p):
        return P3(self.x * p.x, self.y * p.y, self.z * p.z)
    def lerp(self, p, t):
        newx = (1 - t) * self.x + (t * p.x)
        newy = (1 - t) * self.y + (t * p.y)
        newz = (1 - t) * self.z + (t * p.z)
        return P3(newx, newy, newz)
    def mapx(self, f):
        return P3(f(self.x), self.y, self.z)
    def mapy(self, f):
        return P3(self.x, f(self.y), self.z)
    def mapz(self, f):
        return P3(self.x, self.y, f(self.z))
    def mapxy(self, f):
        return P3(f(self.x), f(self.y), self.z) 
    def mapxz(self, f):
        return P3(f(self.x), self.y, f(self.z))
    def mapyz(self, f):
        return P3(sel.x, f(self.y), f(self.z))
    def mapxyz(self, f):
        return P3(f(self.x), f(self.y), f(self.z))
    def mapr(self):
        r = math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
        theta = math.acos(self.z, r)
        row = math.atan2(self.x, self.y)
        return P3(r, theta, row)
    def angle(self):
        r = math.sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
        theta = math.acos(self.z, r)
        row = math.atan2(self.x, self.y)
        return P2(theta, row).str
    def dot(self, p):
        return self.x * p.x + self.y * p.y + self.z * p.z
    def cross(self, p):
        return P3(self.y * p.z - self.z * p.y, self.z * p.x - self.x * p.z, self.x * p.y - self.y * p.x)

uscale = lambda s: lambda x: x*s
translate = lambda p: lambda x: x-p
scale2 = lambda p1: lambda p2: p1.scale2(p2)
mapx = lambda p1: lambda f: p1.mapx(f)
mapy = lambda p1: lambda f: p1.mapy(f)
mapxy = lambda p1: lambda f: p1.mapxy(f)
mapr = lambda p1: lambda f: p1.mapr(f)
rotate = lambda theta: lambda p1: p1.rotate(theta)
scale = lambda p1: lambda c: c * p1
dot = lambda p1, p2: p1.dot(p2)
cross = lambda p1, p2: p1.cross(p2)
fromPolar = lambda r, theta: P2(r*cos(theta), r*sin(theta))
toPolar = lambda p: (abs(p), p.angle())

def lerp(t, p1, p2):
    if isinstance(p1, (int, float)):
        return (1-t)*p1 + t*p2
    return p1.lerp(p2,t)

# Arithmetic over functionals
fplus = Infix(lambda f1, f2: lambda x: f1(x) + f2(x))
fminus = Infix(lambda f1, f2: lambda x: f1(x) - f2(x))
ftimes = Infix(lambda f1, f2: lambda x: f1(x) * f2(x))
fhalf = Infix(lambda f1, f2: lambda x: .5*f1(x) + .5*f2(x))

# Numeric functions
one = const(1)
zero = const(0)
frac = id |fminus| floor
step = lambda x: 1 if x>= 0 else 0
xstep = lambda a: step |o| translate(a)
norm = lambda v: v * (1/abs(v))
repeat = lambda d: lambda x: d*frac(x*1.0/d)
maxc = lambda x: lambda y: max(x,y)
minc = lambda x: lambda y: min(x,y)
ramp = maxc(0) |o| minc(1)

pulse = step |fminus| xstep(1)
pulses = pulse |o| repeat(2)
tooth = lambda x: 0 if x < 0 else x if x < 1 else 2-x if x < 2 else 0
teeth = tooth |o| repeat(2)

tounit = lambda x: (x+1)/2 
fromunit = lambda x: (2*x) - 1
pi = math.pi

toPolar = lambda p: (abs(p), p.angle())

fromPolar = lambda r, theta: P2(r*cos(theta), r*sin(theta))
fromCyl = lambda r, theta,z: P3(r*cos(theta), r*sin(theta), z)

zoom = lambda z: lambda p: p/z

def swirl(p):
    (r, theta) = toPolar(p)
    return fromPolar(r, theta + r) # makes the picture swirl the farther away from the origin that part of the picture is.

def swirl2(p):
    (r, theta) = toPolar(p)
    return fromPolar(r, theta + (r*r))

def butterfly(p):
    (t, theta) = toPolar(p)
    t = math.e**(sin(theta))-2*cos(4*theta) + (sin((2*theta - math.pi)/24))**5
    return fromPolar(t, theta)

def circles(p):
    (r, theta) = toPolar(p)
    return fromPolar(sin(r+theta), theta)

sfrac = lambda x: (frac((x-1)/2)*2)-1
fracture = lambda p: P2(sfrac(p.x), sfrac(p.y))

def tile(n,image):
    return lambda p: image(P2(sfrac(n*p.x), sfrac(n*p.y)))



